package tp.badugi.net;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import tp.badugi.net.exceptions.NameTakenException;
import tp.badugi.net.exceptions.NoSuchTableException;
import tp.badugi.server.Action;
import tp.badugi.server.Server;

public class ClientNetworkingChannelTest {
	
	final long TIMEOUT = 200;
	private iServerRequestListener serverListener;
	private iRequestListener requestListener;
	private iStatusUpdateListener updateListener;

	private class BasicServer implements Runnable {
		private ServerSocket serverSocket;
		private int port;
		ServerNetworkingChannel serverNetworkingChannel;
		
		public BasicServer(int port) {
			this.port = port;
		};
		
		@Override
		public void run() {
			try {
				serverSocket = new ServerSocket(port);
				Socket socket = serverSocket.accept();
				serverNetworkingChannel = new ServerNetworkingChannel(serverListener, socket);
				serverSocket.close();
				serverNetworkingChannel.run();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		public void terminate() {
			try {
				serverSocket.close();
			} catch (IOException e) { }
		}
		
		public ServerNetworkingChannel getServerNetworkingChannel() {
			return serverNetworkingChannel;
		}
	}
	
	private class ServerWrapper extends Server implements Runnable {
		private int port;
		
		public ServerWrapper(int port) {
			this.port = port;
		};
		
		@Override
		public void run() {
			try {
				serverSingleton = this;
				ServerSocket serverSocket = new ServerSocket(port);
				while(!Thread.interrupted()) {
					Socket socket = serverSocket.accept();
					ServerNetworkingChannel clientConnection = 
							new ServerNetworkingChannel(serverSingleton, socket);
					new Thread(clientConnection).start();
				}
				serverSocket.close();
			}
			catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private GameStatus getDummyGameStatus() {
		GameStatus gameStatus = new GameStatus();
		
		gameStatus.myPlayerNumber = 3;
		gameStatus.myChipCount = 1000;
		gameStatus.myCards = new String[] {"AS","2C","3H","4D"};
		gameStatus.numberOfPlayers = 1;
		gameStatus.playerStatuses = new boolean[] {true, false};
		gameStatus.playerNames = new String[] {"P1","P2","P3"};
		gameStatus.playerChipCounts = new long[] {1L, 2L, 3L, 4L, 5L};
		gameStatus.playerPotContributions = new long [] {1L, 2L, 3L, 4L, 5L, 6L};
		gameStatus.playerWithDealerButton = 43;
		gameStatus.smallBlind = 234L;
		gameStatus.bigBlind = 232134L;
		gameStatus.bettingRound = 10;
		gameStatus.playerInAction = 9;
		gameStatus.betAmount = 23234112L;
		gameStatus.potSize = 1010101010101L;
		gameStatus.tableName = "Table";
		
		return gameStatus;
	}
	
	@Before
	public void before() throws Exception {
		serverListener = mock(iServerRequestListener.class);
		requestListener = mock(iRequestListener.class);
		updateListener = mock(iStatusUpdateListener.class);
	}
	
	@After
	public void after() throws Exception {
	}
	
	@Test
	public void testConnectDisconnect() throws Exception {
		final int PORT = 2010;
		BasicServer server = new BasicServer(PORT);
		new Thread(server).start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		client1.disconnect();
		server.terminate();
	}

	@Test
	public void testCreateTable() throws Exception {
		final int PORT = 2020;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 2, 10000, 50, 100);
		client1.disconnect();
		serverThread.interrupt();
	}
	
	@Test(expected=NameTakenException.class)
	public void testCreateTableNameTakeException() throws Exception {
		final int PORT = 2021;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 2, 10000, 50, 100);
		
		ClientNetworkingChannel client2 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client2.connect();
		client2.createTable("Table1", "JUnitTestPlayer2", 4, 20000, 50, 100);
		
		
		client1.disconnect();
		client2.disconnect();
		serverThread.interrupt();
	}

	@Test
	public void testGetTableList() throws Exception {
		final int PORT = 2030;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 2, 10000, 50, 100);
		
		ClientNetworkingChannel client2 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client2.connect();
		client2.createTable("Table2", "JUnitTestPlayer2", 4, 20000, 50, 100);
		
		ClientNetworkingChannel client3 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client3.connect();
		String[] tableList = client3.getTableList();
		String[] expectedTableList = new String[] {"Table1", "Table2"};
		assertTrue(Arrays.equals(expectedTableList, tableList));
		
		client1.disconnect();
		client2.disconnect();
		client3.disconnect();
		serverThread.interrupt();
	}

	@Test
	public void testJoinTable() throws Exception
	{
		final int PORT = 2040;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 4, 10000, 50, 100);
		
		ClientNetworkingChannel client2 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client2.connect();
		client2.joinTable("Table1", "JUnitTestPlayer2");
		
		
		client1.disconnect();
		client2.disconnect();
		serverThread.interrupt();
	}
	
	@Test(expected = NoSuchTableException.class)
	public void testJoinTableNoSuchTableException() throws Exception
	{
		final int PORT = 2041;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 2, 10000, 50, 100);
		
		ClientNetworkingChannel client2 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client2.connect();
		client2.joinTable("Table2", "JUnitTestPlayer2");
		
		
		client1.disconnect();
		client2.disconnect();
		serverThread.interrupt();
	}

	@Test
	public void testLeaveTable() throws Exception {
		final int PORT = 2050;
		ServerWrapper server = new ServerWrapper(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = 
				new ClientNetworkingChannel("127.0.0.1", PORT, requestListener, updateListener);
		client1.connect();
		client1.createTable("Table1", "JUnitTestPlayer", 2, PORT, 50, 100);
		client1.leaveTable();
		client1.createTable("Table2", "JUnitTestPlayer", 2, PORT, 50, 100);
		
		client1.disconnect();
		serverThread.interrupt();
	}

	@Test
	public void testHandleWinnerRevealedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2060;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String[][] revealedCards = 
				new String[][] {{"AH", "AH", "AH", "AH"},{"EH", "EH", "EH", "EH"}};
		int[] winnerIndexes = new int[] {1,3};
		String[] playerNames = new String[] {"Player1", "Player2"};
		long[] updatedChipCounts = new long[] {1000, 2000};
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyWinnerRevealed(
				revealedCards, 
				winnerIndexes, 
				playerNames, 
				updatedChipCounts);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).winnerRevealed(
				(int[]) any(),
				(String[]) any(),
				(String[][]) any(),
				(long[]) any());
	}
	
	@Test
	public void testHandlePlayerWithButtonChangedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2070;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		int playerIndex = 2;
		String[] playerNames = new String[] {"Player1", "Player2"};
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerWithButtonChanged(playerIndex, playerNames);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerWithButtonChanged(anyInt(), (String[]) any());
	}
	
	@Test
	public void testHandlePlayerReplacedCardsUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2080;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String playerName = "Player1";
		int cardCount = 2;
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerReplacedCards(playerName, cardCount);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerReplacedCards(playerName, cardCount);
	}
	
	@Test
	public void testHandlePlayerReconnectedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2090;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String playerName = "Player1";
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerReconnected(playerName);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerReconnected(playerName);
	}
	
	@Test
	public void testHandlePlayerPerformedActionUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2100;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		int playerIndex = 2;
		String action = iRequestListener.ACTION_BET;
		long amount = 10000;
		GameStatus gameStatus = getDummyGameStatus();
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerPerformedAction(playerIndex, action, amount, gameStatus);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerPerformedAction(
				anyInt(), anyString(), anyLong(), (GameStatus) any());
	}
	
	@Test
	public void testHandlePlayerLeftUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2110;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String playerName = "Player1";
		GameStatus gameStatus = getDummyGameStatus();
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerLeft(playerName, gameStatus);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerLeft(anyString(), (GameStatus) any());
	}
	
	@Test
	public void testHandlePlayerJoinedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2120;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		int playerIndex = 2;
		GameStatus gameStatus = getDummyGameStatus();
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerJoined(playerIndex, gameStatus);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerJoined(anyInt(), (GameStatus) any());
	}
	
	@Test
	public void testHandlePlayerInActionChangedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2130;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		int playerIndex = 1;
		String[] playerNames = new String[] {"Player1", "Player2"};
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerInActionChanged(playerIndex, playerNames);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerInActionChanged(playerIndex, playerNames);
	}
	
	@Test
	public void testHandlePlayerDisconnectedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2140;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String playerName = "Player1";
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyPlayerDisconnected(playerName);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).playerDisconnected(playerName);
	}
	
	@Test
	public void testHandleBettingRoundChangedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2150;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		int bettingRound = 2;
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyBettingRoundChanged(bettingRound);
		
		// Clean up
		Thread.sleep(TIMEOUT+500);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).bettingRoundChanged(bettingRound);
	}
	
	@Test
	public void testHandleCardsReceivedUpdate() throws Exception {
		// Prepare test case
		final int PORT = 2160;
		BasicServer server = new BasicServer(PORT);
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		Thread.sleep(TIMEOUT);
		ClientNetworkingChannel client1 = new ClientNetworkingChannel("127.0.0.1", PORT);
		iRequestListener requestListener = mock(iRequestListener.class);
		iStatusUpdateListener updateListener = mock(iStatusUpdateListener.class);
		client1.addRequestListener(requestListener);
		client1.addStatusUpdateListener(updateListener);
		client1.connect();
		
		// Prepare request
		String[] cards = new String[] {"AH", "AH", "AH", "AH"};
		
		// Send request
		Thread.sleep(TIMEOUT);
		ServerNetworkingChannel serverNetChannel = server.getServerNetworkingChannel();
		serverNetChannel.notifyCardsReceived(cards);
		
		// Clean up
		Thread.sleep(TIMEOUT);
		client1.disconnect();
		serverThread.interrupt();
		
		// Verify
		verify(updateListener).receivedCards((String[]) any());
	}
}
